#include <iostream>
#include <fstream>
#include <memory>
#include <thread>
#include <iomanip>
#include <atomic>
#include <libcamera/camera_manager.h>
#include <libcamera/camera.h>
#include <libcamera/framebuffer_allocator.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
#include <libcamera/libcamera.h>
#include <libcamera/framebuffer.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include "image.h"
#include "file_sink.h"
#include "host_uvc.h"


using namespace libcamera;
using namespace std::chrono_literals;
static std::shared_ptr<Camera> camera;
std::atomic<bool> capture_active{true};
StreamConfiguration* p_streamConfig=nullptr;
::std::string tmpString;
//std::unique_ptr<FileSink> sink;
//std::string pattern_;
//std::map<const libcamera::Stream *, std::string> streamNames_;
std::map<libcamera::FrameBuffer *, std::unique_ptr<Image>> mappedBuffers_;

uvc_ctrl_info ctrl_info = {0};

void mapBuffer(FrameBuffer *buffer)
{
	std::unique_ptr<Image> image =
		Image::fromFrameBuffer(buffer, Image::MapMode::ReadOnly);
	assert(image != nullptr);
	std::cout << " mapbuffer complete \n ";
	mappedBuffers_[buffer] = std::move(image);
}

void writeBuffer(const Stream *stream, FrameBuffer *buffer,
			   [[maybe_unused]] const ControlList &metadata)
{
	//std::string filename = pattern_;
	//size_t pos;
	int fd, ret = 0;

    ot_size pic_size;
    td_u32 stride = p_streamConfig->stride;
	tmpString= p_streamConfig->pixelFormat.toString();
   
	const td_char *type_name = tmpString.c_str();
    pic_size.width = p_streamConfig->size.width;
    pic_size.height = p_streamConfig->size.height;
	std::cout << " stride= " << stride ; 
	std::cout << " type_name= " << type_name ; 
	std::cout << " width= " <<  pic_size.width; 
	std::cout << " height= " << pic_size.height << std::endl;
	
	Image *image = mappedBuffers_[buffer].get();

	//fd = open(filename.c_str(), O_CREAT | O_WRONLY |
	//	  (pos == std::string::npos ? O_APPEND : O_TRUNC),
	//	  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	std::cout << " capture image\n ";
	//fd = open("/mnt/nfs/caputre.yuv", O_CREAT | O_WRONLY | O_APPEND,
    fd = open(ctrl_info.pattern, O_CREAT | O_WRONLY | O_APPEND,
		  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	if (fd == -1) {
		ret = -errno;
		std::cerr << "failed to open file " <<  ": "
			  << strerror(-ret) << std::endl;
		return;
	}
	std::cout << " buffer->planes().size()= " << buffer->planes().size()<< std::endl;
	for (unsigned int i = 0; i < buffer->planes().size(); ++i) {
		/*
		 * This was formerly a local "const FrameMetadata::Plane &"
		 * however this causes a false positive warning for dangling
		 * references on gcc 13.
		 */
		const unsigned int bytesused = buffer->metadata().planes()[i].bytesused;
		std::cout << " --i=  " << i << std::endl;
		Span<uint8_t> data = image->data(i);
		const unsigned int length = std::min<unsigned int>(bytesused, data.size());
		std::cout << " length=  " << length << "  data.size=" << data.size() << std::endl;
		if (bytesused > data.size())
			std::cerr << "payload size " << bytesused
				  << " larger than plane size " << data.size()
				  << std::endl;

		ret = ::write(fd, data.data(), length);
		if (ret < 0) {
			ret = -errno;
			std::cerr << "write error: " << strerror(-ret)
				  << std::endl;
			break;
		} else if (ret != (int)length) {
			std::cerr << "write error: only " << ret
				  << " bytes written instead of "
				  << length << std::endl;
			break;
		}
	}

	close(fd);
}
bool processRequest(Request *request)
{
	for (auto [stream, buffer] : request->buffers())
		writeBuffer(stream, buffer, request->metadata());

	return true;
}
    //Handle request completion events
static void requestComplete(Request *request)
{
    if (request->status() == Request::RequestCancelled)
        return;
    const std::map<const Stream *, FrameBuffer *> &buffers = request->buffers();
	for (auto bufferPair : buffers) {
        FrameBuffer *buffer = bufferPair.second;
        const FrameMetadata &metadata = buffer->metadata();
        std::cout << " seq: " << std::setw(6) << std::setfill('0') << metadata.sequence << " bytesused: ";
        unsigned int nplane = 0;
        for (const FrameMetadata::Plane &plane : metadata.planes())
        {
            std::cout << plane.bytesused;
            if (++nplane < metadata.planes().size()) 
                std::cout << "/";
        }
        std::cout << std::endl;
    }
	
	processRequest(request);
	
    request->reuse(Request::ReuseBuffers);
    camera->queueRequest(request);
    
}

int main(int argc,char *argv[]) {
    int ret;
	ret = sample_host_uvc(argc, argv, &ctrl_info);
    if(ret != TD_SUCCESS) {
        return TD_FAILURE;
    }
	
    //init  CmeraManager
    std::unique_ptr<CameraManager> cm = std::make_unique<CameraManager>();
    if (cm->start()) {
        std::cerr << "Failed to start camera manager" << std::endl;
        return 1;
    }
    for (auto const &camera : cm->cameras())
        std::cout << camera->id() << std::endl;

    // Create and acquire a camera
    auto cameras = cm->cameras();
    if (cameras.empty()) {
        std::cout << "No cameras were identified on the system."
              << std::endl;
        cm->stop();
    	return EXIT_FAILURE;
    }

    std::string cameraId = cameras[0]->id();
    camera = cm->get(cameraId);
    camera->acquire();

    //Configure the camera
    std::unique_ptr<CameraConfiguration> config = camera->generateConfiguration( { StreamRole::Viewfinder } );
    StreamConfiguration &streamConfig = config->at(0);
    p_streamConfig =&streamConfig;
    std::cout << "Default viewfinder configuration is: " << streamConfig.toString() << std::endl;
    //Change and validate the configuration
    const td_char *type_name = sample_uvc_v4l2_format_name(ctrl_info.pixelformat); 
    std::cout << "ctrl_info.pixelformat" << ctrl_info.pixelformat << std::endl;  
    if(strcmp(type_name, "YUYV") == 0) {
        streamConfig.pixelFormat = formats::YUYV;
    } else if(strcmp(type_name, "MJPEG") == 0) {
        streamConfig.pixelFormat = formats::MJPEG;
    } else if(strcmp(type_name, "NV21") == 0) {
        streamConfig.pixelFormat = formats::NV21;
    } else {
        streamConfig.pixelFormat = formats::YUYV;
    }
    
    streamConfig.size.width = ctrl_info.width;
    streamConfig.size.height = ctrl_info.height;
    config->validate();
    std::cout << "Validated viewfinder configuration is: " << streamConfig.toString() << std::endl;
    camera->configure(config.get());

    //Allocate FrameBuffers
        //Using the libcamera FrameBufferAllocator
    FrameBufferAllocator *allocator = new FrameBufferAllocator(camera);
    for (StreamConfiguration &cfg : *config) {
        int ret = allocator->allocate(cfg.stream());
        if (ret < 0) {
            std::cerr << "Can't allocate buffers" << std::endl;
            return -ENOMEM;
        }

        size_t allocated = allocator->buffers(cfg.stream()).size();
        std::cout << "Allocated " << allocated << " buffers for stream" << std::endl;
    }

    //Frame Capture
	Stream *stream = streamConfig.stream();
    const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator->buffers(stream);
    std::vector<std::unique_ptr<Request>> requests;
	std::cout << "buffers.size(): " << buffers.size() << std::endl;
    for (unsigned int i = 0; i < buffers.size(); ++i) {
        std::unique_ptr<Request> request = camera->createRequest();
        if (!request)
        {
            std::cerr << "Can't create request" << std::endl;
            return -ENOMEM;
        }

        const std::unique_ptr<FrameBuffer> &buffer = buffers[i];
        int ret = request->addBuffer(stream, buffer.get());
        if (ret < 0)
        {
            std::cerr << "Can't set buffer for request"
                << std::endl;
            return ret;
        }
		std::cout << " start -----mapbuffer----- \n ";
		mapBuffer(buffer.get());
        requests.push_back(std::move(request));
    }

    
    //Request queueing
    camera->start();
    for (std::unique_ptr<Request> &request : requests)
        camera->queueRequest(request.get());
    
    //Event handling and callbacks
    camera->requestCompleted.connect(requestComplete);
    //Event processing
    std::this_thread::sleep_for(std::chrono::milliseconds(ctrl_info.milliseconds));
   
    //Clean up and stop the application¶
    camera->stop();
    allocator->free(stream);
    delete allocator;
    camera->release();
    camera.reset();
    cm->stop();


    return 0;
}
